package com.wodan.zhongjinjicang.common.orm.metadata;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import com.wodan.zhongjinjicang.common.orm.annotation.FieldType;
import com.wodan.zhongjinjicang.common.orm.utils.BeanUtils;
import com.wodan.zhongjinjicang.common.orm.utils.CaseInsensitiveString;
import org.apache.commons.lang.StringUtils;
import org.springframework.util.Assert;
import com.google.common.collect.Lists;


import com.wodan.zhongjinjicang.common.orm.annotation.FieldType;
import com.wodan.zhongjinjicang.common.orm.annotation.Table;
import com.wodan.zhongjinjicang.common.orm.annotation.TableColumn;
import com.wodan.zhongjinjicang.common.orm.utils.BeanUtils;
import com.wodan.zhongjinjicang.common.orm.utils.BeanUtils.List2MapFieldExtractor;
import com.wodan.zhongjinjicang.common.orm.utils.CaseInsensitiveString;

public class TableMetaData {
	private final Class<?> tableClass;

	private final String tableName;

	private final TableFieldMetaData idField;

	private final List<TableFieldMetaData> fieldMetaDataList;

	private final String[] allColumnNames;

	private final Map<CaseInsensitiveString, TableFieldMetaData> columnNameMap;

	private final Map<String, TableFieldMetaData> fieldNameMap;

	public TableMetaData(Class<?> tableClass) {
		Assert.notNull(tableClass);
		Assert.isTrue(!tableClass.isInterface());

		this.tableClass = tableClass;

		this.tableName = resolveTableName();
		this.fieldMetaDataList = resolveFieldMetaDataList();
		this.idField = resolveIdField();
		this.allColumnNames = resolveAllColumnNames();
		this.columnNameMap = resolveColumnNameMap();
		this.fieldNameMap = resolveFieldNameMap();
	}

	private Map<String, TableFieldMetaData> resolveFieldNameMap() {
		return BeanUtils.list2Map(fieldMetaDataList,
				new List2MapFieldExtractor<String, TableFieldMetaData, TableFieldMetaData>() {
					@Override
					public String getKey(TableFieldMetaData bean) {
						return bean.getFieldName();
					}

					@Override
					public TableFieldMetaData getValue(TableFieldMetaData bean) {
						return bean;
					}
				});
	}

	private Map<CaseInsensitiveString, TableFieldMetaData> resolveColumnNameMap() {
		return BeanUtils.list2Map(fieldMetaDataList,
				new List2MapFieldExtractor<CaseInsensitiveString, TableFieldMetaData, TableFieldMetaData>() {
					@Override
					public CaseInsensitiveString getKey(TableFieldMetaData bean) {
						return bean.getColumnName();
					}

					@Override
					public TableFieldMetaData getValue(TableFieldMetaData bean) {
						return bean;
					}
				});
	}

	private String[] resolveAllColumnNames() {
		List<String> resultList = Lists.newArrayListWithCapacity(fieldMetaDataList.size());
		for (TableFieldMetaData field : fieldMetaDataList) {
			resultList.add(field.getColumnName().toString());
		}

		return resultList.toArray(new String[resultList.size()]);
	}

	private TableFieldMetaData resolveIdField() {
		for (TableFieldMetaData metaData : fieldMetaDataList) {
			if (metaData.getFieldType().equals(FieldType.ID)) {
				return metaData;
			}
		}

		return null;
	}

	private List<TableFieldMetaData> resolveFieldMetaDataList() {
		List<TableFieldMetaData> resultList = Lists.newArrayList();

		Field[] declaredFields = tableClass.getDeclaredFields();
		for (Field declaredField : declaredFields) {
			TableColumn tableColumn = declaredField.getAnnotation(TableColumn.class);
			if (tableColumn == null) {
				continue;
			}

			String columnName = tableColumn.columnName();
			if (StringUtils.isBlank(columnName)) {
				throw new IllegalArgumentException("Field[" + declaredField.getName() + "]必须配置columnName属性");
			}

			FieldType fieldType = tableColumn.fieldType();
			if (fieldType == null) {
				throw new IllegalArgumentException("Field[" + declaredField.getName() + "]必须配置fieldType属性");
			}

			PropertyDescriptor propertyDescriptor = BeanUtils
					.getPropertyDescriptor(tableClass, declaredField.getName());
			Method readMethod = propertyDescriptor.getReadMethod();
			if (readMethod == null) {
				throw new IllegalArgumentException("Field[" + declaredField.getName() + "]必须配置get方法");
			}

			Method writeMethod = propertyDescriptor.getWriteMethod();
			if (writeMethod == null) {
				throw new IllegalArgumentException("Field[" + declaredField.getName() + "]必须配置set方法");
			}

			Class<?> propertyType = propertyDescriptor.getPropertyType();

			TableFieldMetaData fieldMetaData = new TableFieldMetaData();
			fieldMetaData.setColumnName(CaseInsensitiveString.valueOf(columnName));
			fieldMetaData.setFieldName(declaredField.getName());
			fieldMetaData.setReadMethod(readMethod);
			fieldMetaData.setWriteMethod(writeMethod);
			fieldMetaData.setFieldType(fieldType);
			fieldMetaData.setFieldObjetClass(propertyType);

			resultList.add(fieldMetaData);
		}

		return Collections.unmodifiableList(resultList);
	}

	private String resolveTableName() {
		Table tableAnnotation = tableClass.getAnnotation(Table.class);
		if (tableAnnotation == null) {
			throw new IllegalArgumentException("实体类必须标记为@Table");
		}

		String tableName = tableAnnotation.tableName();
		Assert.hasText(tableName);

		return tableName;
	}

	public String getTableName() {
		return tableName;
	}

	public List<TableFieldMetaData> getFieldMetaDataList() {
		return fieldMetaDataList;
	}

	public TableFieldMetaData getIdField() {
		return idField;
	}

	public String[] getAllColumnNames() {
		return allColumnNames;
	}

	public TableFieldMetaData getFieldByColumnName(String columnName) {
		Assert.hasText(columnName);

		return columnNameMap.get(CaseInsensitiveString.valueOf(columnName));
	}

	public TableFieldMetaData getFieldByFieldName(String fieldName) {
		Assert.hasText(fieldName);

		return fieldNameMap.get(fieldName);
	}

	public Object instantiateNewEntityBean() {
		return BeanUtils.instantiate(tableClass);
	}

	public Map<String, Object> convertFieldMapToColumnMap(Map<String, Object> fieldNameValueMap,
			Comparator<String> keyComparator) {
		Map<String, Object> resultMap = new TreeMap<>(keyComparator);

		for (Map.Entry<String, Object> entry : fieldNameValueMap.entrySet()) {
			String fieldName = entry.getKey();
			TableFieldMetaData fieldMetaData = this.fieldNameMap.get(fieldName);
			if (fieldMetaData == null) {
				throw new IllegalStateException("实体bean[" + tableClass + "]中的字段[" + fieldName + "]未配置与数据库字段的映射关系");
			}

			CaseInsensitiveString columnName = fieldMetaData.getColumnName();
			resultMap.put(columnName.toString(), entry.getValue());
		}

		return resultMap;
	}

	public boolean isFieldNameExist(String name) {
		Assert.hasText(name);

		return fieldNameMap.containsKey(name);
	}

	public Class<?> getTableClass() {
		return tableClass;
	}
}
